package tmt.usercenter.web.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.tmt.annotation.ReflectInvoked;
import com.tmt.annotation.ResourceIdDescriptor;
import com.tmt.annotation.ScopeDescriptor;
import com.tmt.annotation.parser.EnumAnnotationParser;
import com.tmt.datatable.ext.controller.SelectOptions;
import com.tmt.helper.PackageHelper;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import tmt.usercenter.web.Application;
import tmt.usercenter.web.domain.AuthScope;
import tmt.usercenter.web.domain.ClientSystem;
import tmt.usercenter.web.domain.enums.AuthorizedGrantType;
import tmt.usercenter.web.domain.enums.ClientStatus;
import tmt.usercenter.web.domain.enums.NotifyType;
import tmt.usercenter.web.repository.IAuthScopeRepo;
import tmt.usercenter.web.repository.IClientSystemRepo;
import tmt.usercenter.web.vo.HttpResult;
import tmt.usercenter.web.vo.ResourceVo;
import tmt.usercenter.web.vo.ScopeVo;
import tmt.usercenter.web.vo.TreeNodeVo;

import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional(rollbackFor = Exception.class)
public class ClientSystemService extends TmtBaseService<ClientSystem, Long> {

    @Value("${app.protocol}")
    private String appProtocol;

    @Value("${app.address}")
    private String appAddress;

    @Value("${server.port}")
    private String serverPort;

    @Value("${server.context-path}")
    private String serverContextPath;


    /**
     * 资源服务器所在的包名称。
     */
    public static final String RESOURCE_SERVER_PACKAGE_NAME = Application.class.getPackage().getName();

    @Getter
    private IClientSystemRepo clientSystemRepo;

    private IAuthScopeRepo authScopeRepo;

    @Autowired
    public ClientSystemService(IClientSystemRepo clientSystemRepo
            , IAuthScopeRepo authScopeRepo
            , AuthScopeService authScopeService
            , AuthResourceService authResourceService
            , ClientSystemScopeService scopeService
            , ClientSystemResourceService resourceService) {
        super(clientSystemRepo, Arrays.asList(authScopeService, authResourceService, scopeService, resourceService));
        this.clientSystemRepo = clientSystemRepo;
        this.authScopeRepo = authScopeRepo;
    }

    @Override
    public ClientSystem create(ClientSystem object) throws Exception {
        initialRegisteredRedirectUri(object);
        object = super.create(object);
        initialScope(object);
        return object;
    }

    @Override
    public ClientSystem update(ClientSystem object) throws Exception {
        initialRegisteredRedirectUri(object);
        object = super.update(object);
        initialScope(object);
        return object;
    }

    @Transactional(readOnly = true)
    //@Cacheable(value = "clientSystem", key = "'clientSystem_findByName_' + #clientSystemName", unless = "#result == null")
    public ClientSystem findByName(String clientSystemName) {
        return clientSystemRepo.findByName(clientSystemName);
    }

    @Transactional(readOnly = true)
    //@Cacheable(value = "clientSystem", key = "'clientSystem_findByClientId_' + #clientId", unless = "#result == null")
    @ReflectInvoked
    public ClientSystem findByClientId(String clientId) {
        return clientSystemRepo.findByClientId(clientId);
    }

    @Transactional(readOnly = true)
    //@Cacheable(value = "clientSystem", key = "'clientSystem_findIdByClientId_' + #clientId", unless = "#result == null")
    public Long findIdByClientId(String clientId) {
        return clientSystemRepo.findIdByClientId(clientId);
    }

	/*public ClientSystem findByRegisteredRedirectUri(String uri){
		return null;
	}*/

    @Transactional(readOnly = true)
    public boolean existsByClientId(String clientId) {
        return clientSystemRepo.existsByClientId(clientId);
    }

    @Transactional(readOnly = true)
    public boolean existsByClientName(String name) {
        return clientSystemRepo.existsByName(name);
    }

    @Transactional(readOnly = true)
    public SelectOptions findSelectOptionsForAuthorizedGrantTypes() {
        SelectOptions selectOptions = new SelectOptions();
        Arrays.asList(AuthorizedGrantType.values()).stream().forEach(x -> {
            Map<String, Object> map = new HashMap<>();
            map.put("id", x.name());
            map.put("text", EnumAnnotationParser.getTitle(x));
            map.put("attr", new HashMap<String, Object>() {{
                put("title", EnumAnnotationParser.getDescription(x));
            }});
            selectOptions.addItem(map);
        });
        return selectOptions;
    }

    @Transactional(readOnly = true)
    public List<TreeNodeVo> findScopeTree(String clientId) {

        //1.1 获取资源ID描述集合。
        Map<Object, ResourceIdDescriptor> riDescriptors = PackageHelper.findAnnotionInstance(RESOURCE_SERVER_PACKAGE_NAME,
                EnableResourceServer.class, ResourceIdDescriptor.class);

        //1.2 构造ResourceVo对象。
        Vector<ResourceVo> riList = new Vector<>();
        riDescriptors.keySet().forEach(x -> {
            ResourceIdDescriptor ri = riDescriptors.get(x);
            riList.add(new ResourceVo(x, ri.title(), ri.description(), ri.order(), ri.defaultSelected(), ri.scope()));
        });

        Vector<TreeNodeVo> treeNodes = new Vector<>();

        //2. 查询客户系统对象。
        ClientSystem clientSystem = (clientId != null && clientId.trim().length() > 0) ? clientSystemRepo.findByClientId(clientId) : null;

        //3.1 获取Scope注解对象列表。
        List<ScopeDescriptor> descriptors = PackageHelper.findAnnotionInstance(RESOURCE_SERVER_PACKAGE_NAME, ScopeDescriptor.class);
        descriptors = descriptors.stream().sorted(Comparator.comparing(ScopeDescriptor::order)).collect(Collectors.toList());

        //3.2 构造TreeNodeVo对象，并放入treeNodes列表中。
        descriptors.forEach(x -> {
            boolean selected = clientSystem != null && clientSystem.getScope() != null && clientSystem.getScope().contains(x.value());
            TreeNodeVo treeNodeVo = new TreeNodeVo(x.value(), x.title(), x.description(), selected, x.defaultSelected());
            treeNodes.add(treeNodeVo);
        });

        //4. 为每个TreeNodeVo对象配置ResourceVo对象。
        treeNodes.forEach(x -> {
            List<ResourceVo> rList = riList.stream().filter(y -> Arrays.asList(y.getScope()).contains(x.getValue())).collect(Collectors.toList());
            rList.stream().sorted(Comparator.comparing(ResourceVo::getOrder))
                    .parallel().forEach(y -> {
                boolean selected = clientSystem != null && clientSystem.getResourceIds() != null
                        && clientSystem.getResourceIds().contains(y.getValue().toString());
                TreeNodeVo treeNodeVo = new TreeNodeVo(y.getValue().toString(), y.getTitle(), y.getDescription(),
                        selected, selected);
                x.getNodes().add(treeNodeVo);
            });

        });

        return treeNodes;
    }

    /**
     * 在指定的包下的所有类中，查找标注了指定注解类的静态字段的ScopeInfo信息，并转换为SelectOptions对象。
     *
     * @return
     */
    @Transactional(readOnly = true)
    public SelectOptions findScopeOptionsByFieldNameAndAnnotation() {
        SelectOptions selectOptions = new SelectOptions();

        List<ScopeDescriptor> descriptors = PackageHelper.findAnnotionInstance(RESOURCE_SERVER_PACKAGE_NAME, ScopeDescriptor.class);
        descriptors.stream()
                .sorted(Comparator.comparing(ScopeDescriptor::order))
                .parallel().forEach(x -> {
            Map<String, Object> map = new HashMap<>();
            map.put("id", x.value());
            map.put("text", x.title());
            map.put("attr", new HashMap<String, Object>() {{
                put("title", x.description());
            }});
            selectOptions.addItem(map);
        });

        return selectOptions;
    }

    /**
     * 在指定的包下的所有类中，查找标注了指定注解类的静态字段的ScopeInfo信息，并转换为SelectOptions对象。
     *
     * @param scope
     * @return
     */
    @Transactional(readOnly = true)
    public SelectOptions findResourceIdOptionsByFieldNameAndAnnotation(String scope) {
        SelectOptions selectOptions = new SelectOptions();

        Map<Object, ResourceIdDescriptor> riDescriptors = PackageHelper.findAnnotionInstance(RESOURCE_SERVER_PACKAGE_NAME,
                EnableResourceServer.class, ResourceIdDescriptor.class);

        Vector<ResourceVo> riList = new Vector<>();
        riDescriptors.keySet().forEach(x -> {
            ResourceIdDescriptor ri = riDescriptors.get(x);
            riList.add(new ResourceVo(x, ri.title(), ri.description(), ri.order(), ri.defaultSelected(), ri.scope()));
        });

        riList.stream()
                .sorted(Comparator.comparingInt(ResourceVo::getOrder))
                .parallel().forEach(x -> {
            Map<String, Object> map = new HashMap<>();
            map.put("id", x.getValue());
            map.put("text", MessageFormat.format("{0}({1})", x.getTitle(), x.getValue()));
            map.put("attr", new HashMap<String, Object>() {{
                put("title", x.getDescription());
            }});
            selectOptions.addItem(map);
        });

        return selectOptions;
    }

    /***
     * 获取 client 的 scopes
     * @param clientId 客户端编号
     * @return scopes of the client
     */
    @Transactional(readOnly = true)
    public List<String> findClientScopes(String clientId) {
        ClientSystem cs = findByClientId(clientId);
        return cs.getScope();
    }

    @Transactional(readOnly = true)
    public List<ScopeVo> findAllScopes() {
        List<ScopeDescriptor> descriptors = PackageHelper.findAnnotionInstance(RESOURCE_SERVER_PACKAGE_NAME, ScopeDescriptor.class);
        List<ScopeVo> scopeVoList = new ArrayList<>();
        descriptors.stream().sorted(Comparator.comparing(ScopeDescriptor::order))
                .forEachOrdered(x -> scopeVoList.add(new ScopeVo(x.title(), x.value(), x.description(), x.order(), x.defaultSelected())));

        return scopeVoList;
    }

    @Transactional(readOnly = true)
    public ClientSystem applyClient(ClientSystem newClient) {

        newClient.setStatus(ClientStatus.APPLYING);
        newClient.setClientSecret(randomSecret());

        return null;
    }

    @Transactional(readOnly = true)
    public String randomSecret() {
        String availableStr = "abcdefghijklmnopqrstuvwxyz0123456789";
        int defaultLength = 40;

        Random random = new Random();
        StringBuilder sb = new StringBuilder(defaultLength);
        for (int i = 0; i < defaultLength; i++) {
            sb.append(availableStr.charAt(random.nextInt(availableStr.length())));
        }

        return sb.toString();
    }

    @Transactional(readOnly = true)
    public void sendLogoutNotifyToClient(String clientId, Long userId) {
        //通知该用户登录的系统:强制该用户登出。
        try {
            ClientSystem cs = findByClientId(clientId);
            String url = cs.getLogoutNotifyUrl();
            url = String.format(url, clientId, userId);
            RestTemplate template = new RestTemplate();
            HttpResult result = template.getForObject(url, HttpResult.class);
            logger.debug(String.format("------通知系统[%s]用户已登出--------%s", cs.getName(), result.toString()));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Transactional(readOnly = true)
    public void sendNotifyToClient(String clientId, NotifyType notifyType, Object notifyData) {
        try {
            ClientSystem cs = findByClientId(clientId);
            String url = cs.getChangedNotifyUrl();
            if (url.toLowerCase().startsWith("http://") || url.toLowerCase().startsWith("https://")) {
                url = String.format(url, notifyType.name());

                RestTemplate template = new RestTemplate();

                HttpHeaders headers = new HttpHeaders();
                MediaType type = MediaType.APPLICATION_JSON_UTF8;
                headers.setContentType(type);
                headers.add("Accept", MediaType.APPLICATION_JSON.toString());

                String json = JSONObject.toJSONString(notifyData);
                HttpEntity<JSONObject> data = new HttpEntity<>(JSON.parseObject(json), headers);

                ResponseEntity<HttpResult> result = template.postForEntity(url, data, HttpResult.class);
                logger.debug(String.format("------通知系统[%s：]新用户注册信息--------%s", cs.getName(), result.toString()));
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void initialRegisteredRedirectUri(ClientSystem cs) {
        if (cs == null)
            return;

        if (CollectionUtils.isEmpty(cs.getRegisteredRedirectUri())) {
            cs.setRegisteredRedirectUri(Collections.singletonList(
                    MessageFormat.format("{0}://{1}:{2}{3}/login",
                            appProtocol, appAddress, serverPort, serverContextPath)));
        }

        if (StringUtils.isEmpty(cs.getChangedNotifyUrl())) {
            cs.setChangedNotifyUrl(MessageFormat.format("{0}://{1}:{2}{3}/external/notify/%s",
                    appProtocol, appAddress, serverPort, serverContextPath));
        }

        if (StringUtils.isEmpty(cs.getLogoutNotifyUrl())) {
            cs.setLogoutNotifyUrl(MessageFormat.format("{0}://{1}:{2}{3}/external/logout/%s/%s",
                    appProtocol, appAddress, serverPort, serverContextPath));
        }
    }

    private void initialScope(ClientSystem cs) {
        List<String> scopes = new ArrayList<>();
        cs.getClientSystemScopes().forEach(x -> {
            if (x.getAuthScope().getId() != null) {
                AuthScope authScope = authScopeRepo.findById(x.getAuthScope().getId());
                if (authScope != null && authScope.getScope() != null) {
                    scopes.add(authScope.getScope());
                }
            }
        });

        if (!scopes.isEmpty()) {
            cs.setScope(scopes);
            clientSystemRepo.save(cs);
        }
    }

}
